Card one
Half width via .col-6.
teff styles plain, accessible HTML and sprinkles behavior on top with vanilla web components. No framework, no build step, no dependencies — drop in a stylesheet and go. Inspired by Oat CSS.
<link rel="stylesheet" href="https://unpkg.com/@thinking.tools/teff/teff.min.css">
<script src="https://unpkg.com/@thinking.tools/teff/teff.min.js"></script>
<!-- or --> npm install @thinking.tools/teff
* with a little extra flavor — like the grain it's named after.
Every color resolves through light-dark(), so dark
mode is automatic. Customize by overriding a handful of custom
properties — or flip the presets in the top bar: accent, radius,
density, theme.
<html data-accent="blue" data-radius="soft" data-density="compact">
/* or override tokens directly */
:root {
--primary: light-dark(#2068c9, #7ab3ff);
--radius-button: 0.75rem;
}
Prose needs no class names — semantic HTML is styled out of the box.
An hgroup pairs it with a subtitle.
Body text with a link,
bold, italic,
inline code, highlight and
small print.
Press Cmd + K to search.
Deleted and inserted text,
no longer accurate, one mispelled word, an
HTML
abbreviation, H2O, e=mc2 and
program output.
The best class name is the one you never had to write.
Status labels: a .badge span with the same
data-variant tones as buttons;
.outline keeps it quiet.
Initials in a figure data-variant="avatar" — size
with .small/.large, nest them to stack
a team.
A 12-column .row/.col-* grid that stacks
on mobile, plus a one-liner .autogrid that fits as
many cards as space allows.
Half width via .col-6.
Cards are shadow-ringed surfaces.
Native inputs, selects, checkboxes, radios, switches, ranges,
file and search fields — styled directly, validation states
included. Field hints and errors hang off
data-field. With the JS bundle on the page, password
fields grow a show/hide toggle — opt out with
data-static.
Wrap a plain <table> in .table for
horizontal scrolling on small screens. Numbers are tabular by
default; <caption> and
<tfoot> are styled too.
| Component | Type | Size |
|---|---|---|
| Tabs | Custom element | 0.9 kB |
| Toast | JS API | 1.1 kB |
| Card | CSS only | 0.1 kB |
| Total | 2.1 kB |
Native <details> — adjacent ones fuse into a
group.
An ultra-lightweight, semantic component library — one CSS file and one JS file, each usable on its own.
No. Link the CSS and JS and write plain semantic HTML.
<teff-tabs> wires up ARIA and roving keyboard
navigation around your existing markup.
Overview panel — arrow keys move between tabs.
Features panel — ARIA wiring is automatic.
Pricing panel — roving tabindex for keyboard nav.
Built on the native popover API —
<teff-dropdown> adds anchoring and menu
keyboard navigation.
Native <dialog> opened declaratively with
command/commandfor. Clicking the
backdrop closes it — opt out with data-static.
A role="alert" div — neutral by default, tinted by
data-variant.
Native <progress> and
<meter> — a bare progress is indeterminate,
and meters self-color by their distance from
optimum.
Set aria-busy="true" on anything to get a spinner;
add data-spinner="overlay" to dim and disable its
contents. The skeleton shimmer runs entirely on the compositor.
A plain title attribute is progressively enhanced
into a styled tooltip.
One imperative call:
teff.toast(message, title, options).
Opt-in, compositor-friendly animations — only
opacity, translate and
filter ever move, and everything respects
prefers-reduced-motion. data-enter
fades an element in; data-enter="stagger" cascades
its children 100 ms apart.
Each child fades, lifts and unblurs in turn.
last one
A quick head-shake for rejected input: call
teff.shake(el) when validation fails, or toggle
data-shake yourself.
A small set of helpers for the gaps between components: stacks,
spacing, truncation, .sr-only, shadows and radii.